/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Operations.Mor;

import Global.Global;
import Operations.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 *
 * @author littlechild
 */
public class OpMor implements Operation{
    static int rgbmask=0xffffff;
    static int threshold=128;
    static int mustBe1=1;
    static int mustBe0=0;
    static int whatever=2;
    public BufferedImage operate(BufferedImage image, double[] paras){
            // thresholding implicitly in the operation.
        BufferedImage binImage = image; //do not need explicitly threshold
        BufferedImage returnImage = null;
        try {
            switch ((int) paras[paras.length-1]) {
                case Global.opErosion:
                    returnImage = erode(binImage);
                    
                    break;
                case Global.opDilation:
                    returnImage = dilate(binImage);
                    break;
                case Global.opOpening:
                    returnImage = dilate(erode(binImage));
                    break;
                case Global.opClosing:
                    returnImage = erode(dilate(binImage));
                    break;
                case Global.opBoundary:
                    returnImage = erode(binImage);
                    BufferedImage tmpImage = new Operations.Bit.OpThreshold().operate(binImage, new double[]{threshold, Global.opThreshold});
                    ImageIO.write(returnImage, "bmp", new File("tmpBoundary.bmp"));
                    returnImage=new Operations.Algebra.OpCptWith().operate(tmpImage,"tmpBoundary.bmp",false);
                    break;
                case Global.opSkeleton:
                    returnImage=thin(binImage,false);
                    break;
                case Global.opThinning:
                    if(paras[0]==Global.thinningContinuous)
                        returnImage=thin(binImage,false);
                    else 
                        returnImage=thin(binImage,true);
                    break;
            }
        } catch (IOException ex) {
            Logger.getLogger(OpMor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return returnImage;
    }

    private BufferedImage erode(BufferedImage binImage) {
        int template[][]=new int [][] {
          new int[]{mustBe1,mustBe1,mustBe1}, 
          new int[]{mustBe1,mustBe1,mustBe1}, 
          new int[]{mustBe1,mustBe1,mustBe1}
        };
        BufferedImage returnImage=new BufferedImage(binImage.getWidth(),binImage.getHeight(),binImage.getType());
        for(int i=0;i!=returnImage.getWidth();++i) {
            for(int j=0;j!=returnImage.getHeight();++j) {
                if(isAndMatching(binImage,i,j,template))
                    returnImage.setRGB(i, j, rgbmask);
            }
        }
        return returnImage;
    }

    private BufferedImage dilate(BufferedImage binImage) {
        int template[][]=new int [][] {
          new int[]{mustBe1,mustBe1,mustBe1}, 
          new int[]{mustBe1,mustBe1,mustBe1}, 
          new int[]{mustBe1,mustBe1,mustBe1}
        };
        BufferedImage returnImage=new BufferedImage(binImage.getWidth(),binImage.getHeight(),binImage.getType());
        for(int i=0;i!=returnImage.getWidth();++i) {
            for(int j=0;j!=returnImage.getHeight();++j) {
                if(isOrMatching(binImage,i,j,template))
                    returnImage.setRGB(i, j, rgbmask);
            }
        }
        return returnImage;
    }

    private boolean isAndMatching(BufferedImage binImage, int x, int y, int[][] template) {
        assert template.length==3;
        assert template[0].length==3;
        for(int i=-1;i!=2;++i) {
            for(int j=-1;j!=2;++j) {
                if(template[1+j][1+i]==mustBe1) {
                    if(getGray(Operations.Geometry.Utilities.getFrom(binImage,i+x,j+y))<=threshold)
                        return false;
                }else if(template[j+1][i+1]==mustBe0) {
                    if(getGray(Operations.Geometry.Utilities.getFrom(binImage,i+x,j+y))>threshold)
                        return false;
                }
            }
        }
        return true;
    }

    private boolean isOrMatching(BufferedImage binImage, int x, int y, int[][] template) {
        assert template.length==3;
        assert template[0].length==3;
        for(int i=-1;i!=2;++i) {
            for(int j=-1;j!=2;++j) {
                if(template[1+j][1+i]==mustBe1) {
                    if(getGray(Operations.Geometry.Utilities.getFrom(binImage,i+x,j+y))>threshold)
                        return true;
                }else if(template[j+1][i+1]==mustBe0) {
                    if(getGray(Operations.Geometry.Utilities.getFrom(binImage,i+x,j+y))<=threshold)
                        return true;
                }
            }
        }
        return false;
    }
    
    public static int getGray(int rgb) {
        return (int)(0.5+0.11*(rgb & 255) + 0.3*((rgb>>>16) & 255) +0.59*((rgb>>>8) & 255));
    }

    private BufferedImage thin(BufferedImage binImage,boolean isSingle) {
        //---->
        //  /
        // *-->
        // 
       int templatesLeft[][][]=new int[][][]{
          new int[][]{
              new int[]{mustBe1,mustBe1,mustBe1}, 
              new int[]{whatever,mustBe1,whatever}, 
              new int[]{mustBe0,mustBe0,mustBe0} 
          },
          new int[][]{
              new int[]{whatever,mustBe1,whatever},
              new int[]{mustBe1,mustBe1,mustBe0},
              new int[]{whatever,mustBe0,mustBe0}
          },
       };
       //^  ^  ^
       //| \| \|
       //|  *  *
       //
       int templatesDown[][][]=new int[][][]{
              new int[][]{
                  new int[]{mustBe1,whatever,mustBe0},
                  new int[]{mustBe1,mustBe1,mustBe0},
                  new int[]{mustBe1,whatever,mustBe0},
              },
              new int[][]{
                  new int[]{whatever,mustBe0,mustBe0},
                  new int[]{mustBe1,mustBe1,mustBe0},
                  new int[]{whatever,mustBe1,whatever}
              }
       };
       //*    |
       //| \  |
       //|  \ |
       //\/   \/
       int templatesUp[][][]=new int[][][]{
          new int [][]{
              new int[]{mustBe0,whatever,mustBe1}, 
              new int[]{mustBe0,mustBe1,mustBe1}, 
              new int[]{mustBe0,whatever,mustBe1}
          },
          new int[][]{
                  new int[]{whatever,mustBe1,whatever},
                  new int[]{mustBe0,mustBe1,mustBe1},
                  new int[]{mustBe0,mustBe0,whatever}
          }
       };
       //<-- *
       //  /
       //<----
       int templatesRight[][][]=new int[][][]{
          new int[][]{
              new int[]{mustBe0,mustBe0,mustBe0}, 
              new int[]{whatever,mustBe1,whatever}, 
              new int[]{mustBe1,mustBe1,mustBe1} 
          },
          new int[][]{
              new int[]{mustBe0,mustBe0,whatever},
              new int[]{mustBe0,mustBe1,mustBe1},
              new int[]{whatever,mustBe1,whatever}
          }
       };
       int templates[][][][]=new int[][][][]{
         templatesUp,templatesDown,templatesLeft,templatesRight  
       };
        BufferedImage returnImage=new BufferedImage(binImage.getWidth(),binImage.getHeight(),binImage.getType());
        boolean isChanged=true;
        for(int i=0;i!=returnImage.getWidth();++i) {
            for(int j=0;j!=returnImage.getHeight();++j) {
                returnImage.setRGB(i, j, binImage.getRGB(i, j));
            }
        }
        isChanged=true;
        while(isChanged) {
            isChanged=false;
            //---->
            //  /
            // *-->
            // 
            for(int j=0;j!=binImage.getHeight();++j) {
                for(int i=0;i!=binImage.getWidth();++i) {
                    for(int m=0;m!=templatesLeft.length;++m) {
                        if(getGray(returnImage.getRGB(i, j))>threshold 
                                && isAndMatching(returnImage,i,j,templatesLeft[m])) {
                            returnImage.setRGB(i, j, 0);
                            isChanged=true;
                            break;
                        }
                    }
                }
            }
           //^  ^  ^
           //| \| \|
           //|  *  *
           //
            for(int i=0;i!=binImage.getWidth();++i) {
                for(int j=binImage.getHeight()-1;j>=0;--j) {
                    for(int m=0;m!=templatesDown.length;++m) {
                        if(getGray(returnImage.getRGB(i, j))>threshold 
                                && isAndMatching(returnImage,i,j,templatesDown[m])) {
                            returnImage.setRGB(i, j, 0);
                            isChanged=true;
                            break;
                        }
                    }
                }
            }
           //*    |
           //| \  |
           //|  \ |
           //\/   \/
           for(int i=binImage.getWidth()-1;i>=0;--i) {
                for(int j=0;j!=binImage.getHeight();++j) {
                    for(int m=0;m!=templatesUp.length;++m) {
                        if(getGray(returnImage.getRGB(i, j))>threshold 
                                && isAndMatching(returnImage,i,j,templatesUp[m])) {
                            returnImage.setRGB(i, j, 0);
                            isChanged=true;
                            break;
                        }
                    }
                }
            }
           //<-- *
           //  /
           //<----
           for(int j=binImage.getHeight()-1;j>=0;--j) {
                for(int i=binImage.getWidth()-1;i>=0;--i) {
                    for(int m=0;m!=templatesRight.length;++m) {
                        if(getGray(returnImage.getRGB(i, j))>threshold 
                                && isAndMatching(returnImage,i,j,templatesRight[m])) {
                            returnImage.setRGB(i, j, 0);
                            isChanged=true;
                            break;
                        }
                    }
                }
            }
            if(isSingle)
                isChanged=false;
        }
        return returnImage;
    }
}

class Couple {
    int x,y;
    Couple(int a,int b) {
        x=a;
        y=b;
    }
}